package cn.south.campus.business.service.impl;

import cn.south.campus.business.mapper.GoodsOrderMapper;
import cn.south.campus.business.model.bo.LoginContext;
import cn.south.campus.business.model.bo.LoginInfo;
import cn.south.campus.business.model.constants.CacheKey;
import cn.south.campus.business.model.entity.Address;
import cn.south.campus.business.model.entity.GoodsOrder;
import cn.south.campus.business.model.enums.CampusErrorEnums;
import cn.south.campus.business.model.enums.OrderEnums;
import cn.south.campus.business.model.enums.StudentEnums;
import cn.south.campus.business.model.enums.WalletEnums;
import cn.south.campus.business.model.req.GoodsOrderCreateReq;
import cn.south.campus.business.model.req.GoodsOrderQueryReq;
import cn.south.campus.business.model.vo.GoodsOrderVo;
import cn.south.campus.business.model.vo.GoodsVo;
import cn.south.campus.business.service.*;
import cn.south.campus.common.cache.CacheService;
import cn.south.campus.common.exceptions.BusinessException;
import cn.south.campus.common.model.enums.CommonEnums;
import cn.south.campus.common.pay.PayFactory;
import cn.south.campus.common.pay.PayService;
import cn.south.campus.common.utils.AssertUtils;
import cn.south.campus.common.utils.SnowflakeIdWorker;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author chow
 * @since 2024-11-14
 */
@Service
@AllArgsConstructor
public class GoodsOrderServiceImpl extends ServiceImpl<GoodsOrderMapper, GoodsOrder> implements GoodsOrderService {

    private final GoodsOrderMapper goodsOrderMapper;

    private final GoodsService goodsService;

    private final StudentService studentService;

    private final AddressService addressService;

    private final CacheService cacheService;

    private final WalletService walletService;

    private final WalletLogService walletLogService;

    private final TransactionTemplate transactionTemplate;

    @Override
    public IPage<GoodsOrderVo> pageBuyer(GoodsOrderQueryReq req) {
        LoginInfo context = LoginContext.getContext();
        req.setBuyer(context.getId());
        IPage<GoodsOrder> pageRes = page(req);
        return pageRes.convert(GoodsOrderVo::convert);
    }

    @Override
    public IPage<GoodsOrderVo> pageSeller(GoodsOrderQueryReq req) {
        LoginInfo context = LoginContext.getContext();
        req.setSeller(context.getId());
        IPage<GoodsOrder> pageRes = page(req);
        return pageRes.convert(GoodsOrderVo::convert);
    }

    private IPage<GoodsOrder> page(GoodsOrderQueryReq req) {
        return goodsOrderMapper.pageQuery(req.getPageDto(), req);
    }

    @Override
    public GoodsOrderVo getSellerOrderDetail(String orderNo) {
        LoginInfo context = LoginContext.getContext();
        GoodsOrder goodsOrder = goodsOrderMapper.querySellerOrder(orderNo, context.getId());
        AssertUtils.isNotNull(goodsOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        return GoodsOrderVo.convert(goodsOrder);
    }

    @Override
    public GoodsOrderVo getBuyerOrderDetail(String orderNo) {
        LoginInfo context = LoginContext.getContext();
        GoodsOrder buyerOrder = getBuyerOrder(orderNo, context.getId());
        return GoodsOrderVo.convert(buyerOrder);
    }

    @Override
    public String createOrder(GoodsOrderCreateReq req) {
        LoginInfo context = LoginContext.getContext();
        Long goodsId = req.getGoodsId();
        int authStatus = studentService.getAuthStatus(context.getId());
        if (authStatus != StudentEnums.AuthStatusEnums.AUTH_SUCCESS.getCode()) {
            throw new BusinessException(CampusErrorEnums.STUDENT_AUTH_ERROR);
        }
        boolean lock = cacheService.lock(CacheKey.GOODS + ":" + goodsId, 1000 * 20);
        if (!lock) {
            throw new BusinessException(CommonEnums.CONCURRENT_ERROR);
        }
        try {
            GoodsVo goods = goodsService.getGoods(goodsId);
            AssertUtils.isNotNull(goods, CommonEnums.DATA_HAS_NOT_ERROR);
            AssertUtils.isGtE(goods.getRemain().longValue(), 1L, CampusErrorEnums.STOCK_NOT_ENOUGH);
            Long addressId = req.getAddressId();
            Address address = addressService.getById(addressId);
            AssertUtils.isNotNull(address, CommonEnums.DATA_HAS_NOT_ERROR);
            GoodsOrder goodsOrder = buildOrder(req, context, goods, address);
            goodsOrderMapper.insert(goodsOrder);
            goods.setRemain(goods.getRemain() - 1);
            goodsService.updateGoods(goods);
            return goodsOrder.getOrderNo();
        } finally {
            cacheService.unLock(CacheKey.GOODS + ":" + goodsId);
        }
    }

    @Override
    public void cancelOrder(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getBuyerOrder(orderNo, userId);
        AssertUtils.isEquals(goodsOrder.getPayStatus(), OrderEnums.OrderPayStatus.UNPAID.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        goodsOrder.setStatus(OrderEnums.OrderStatus.CANCEL.getCode());
        Long goodsId = goodsOrder.getGoodsId();
        GoodsVo goods = goodsService.getGoods(goodsId);
        goods.setRemain(goods.getRemain() + 1);
        goodsService.updateGoods(goods);
        goodsOrderMapper.updateById(goodsOrder);
    }

    @Override
    public void payOrder(String orderNo, String payMethod) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getBuyerOrder(orderNo, userId);
        AssertUtils.isEquals(goodsOrder.getPayStatus(), OrderEnums.OrderPayStatus.UNPAID.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        if (!payMethod.equals("wallet")) {
            PayService payService = PayFactory.getPayService(payMethod);
            payService.pay(orderNo, goodsOrder.getGoodsPrice());
            goodsOrder.setPayStatus(OrderEnums.OrderPayStatus.PAID.getCode());
            goodsOrder.setStatus(OrderEnums.OrderStatus.DELIVER.getCode());
            goodsOrder.setPayTime(LocalDateTime.now());
            goodsOrder.setPayMethod(payMethod);
            goodsOrderMapper.updateById(goodsOrder);
            return;
        }
        //查询钱包
        lockSellerAndBuyerWallet(userId, goodsOrder.getSellerId());
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                //买家扣款
                walletService.subtractBalance(goodsOrder.getGoodsPrice(), userId);
                //记录流水
                walletLogService.insertLog(goodsOrder.getGoodsPrice(), userId, goodsOrder.getOrderNo(), WalletEnums.WalletTypeEnums.PAY, WalletEnums.WalletLogStatusEnums.PAYED);
                walletLogService.insertLog(goodsOrder.getGoodsPrice(), goodsOrder.getSellerId(), goodsOrder.getOrderNo(), WalletEnums.WalletTypeEnums.INCOME, WalletEnums.WalletLogStatusEnums.RECEIVING);
                //卖家冻结余额增款
                walletService.addFreezeBalance(goodsOrder.getGoodsPrice(), goodsOrder.getSellerId());
                goodsOrder.setPayStatus(OrderEnums.OrderPayStatus.PAID.getCode());
                goodsOrder.setStatus(OrderEnums.OrderStatus.DELIVER.getCode());
                goodsOrder.setPayTime(LocalDateTime.now());
                goodsOrder.setPayMethod(payMethod);
                goodsOrderMapper.updateById(goodsOrder);
            });
        } finally {
            cacheService.unLock(CacheKey.WALLET + ":" + userId);
            cacheService.unLock(CacheKey.WALLET + ":" + goodsOrder.getSellerId());
        }
    }

    @Override
    public void confirmDeliver(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getSellerOrder(orderNo, userId);
        AssertUtils.isEquals(goodsOrder.getStatus(), OrderEnums.OrderStatus.DELIVER.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        goodsOrder.setStatus(OrderEnums.OrderStatus.RECEIVE.getCode());
        goodsOrderMapper.updateById(goodsOrder);
    }

    @Override
    public void confirmOrder(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getBuyerOrder(orderNo, userId);
        AssertUtils.isNotNull(goodsOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        AssertUtils.isEquals(goodsOrder.getPayStatus(), OrderEnums.OrderPayStatus.PAID.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        if (!"wallet".equals(goodsOrder.getPayMethod())) {
            goodsOrder.setStatus(OrderEnums.OrderStatus.FINISHED.getCode());
            goodsOrder.setConfirmTime(LocalDateTime.now());
            goodsOrderMapper.updateById(goodsOrder);
            return;
        }
        //查询钱包，锁卖家钱包
        boolean lock = cacheService.spinLock(CacheKey.WALLET + ":" + goodsOrder.getSellerId(), 1000 * 2, 5);
        if (!lock) {
            throw new BusinessException(CommonEnums.CONCURRENT_ERROR);
        }
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                goodsOrder.setStatus(OrderEnums.OrderStatus.FINISHED.getCode());
                goodsOrder.setConfirmTime(LocalDateTime.now());
                goodsOrderMapper.updateById(goodsOrder);
                //卖家余额增加，冻结余额减少
                walletService.subFreezeAddBalance(goodsOrder.getGoodsPrice(), goodsOrder.getSellerId());
                //记录流水
                walletLogService.updateLog(goodsOrder.getSellerId(), goodsOrder.getOrderNo(), WalletEnums.WalletLogStatusEnums.RECEIVED);
            });
        } finally {
            cacheService.unLock(CacheKey.WALLET + ":" + goodsOrder.getSellerId());
        }

    }

    @Override
    public void applyRefund(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getBuyerOrder(orderNo, userId);
        goodsOrder.setStatus(OrderEnums.OrderStatus.REFUNDING.getCode());
        goodsOrderMapper.updateById(goodsOrder);
    }

    @Override
    public void cancelRefund(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getBuyerOrder(orderNo, userId);
        goodsOrder.setStatus(OrderEnums.OrderStatus.FINISHED.getCode());
        goodsOrderMapper.updateById(goodsOrder);
    }

    @Override
    public void refundOrder(String orderNo) {
        Long userId = LoginContext.getContext().getId();
        GoodsOrder goodsOrder = getSellerOrder(orderNo, userId);
        AssertUtils.isNotEquals(goodsOrder.getPayStatus(), OrderEnums.OrderStatus.REFUND.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        //同时锁定买家与卖家钱包
        lockSellerAndBuyerWallet(userId, goodsOrder.getBuyerId());
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                goodsOrder.setStatus(OrderEnums.OrderStatus.REFUND.getCode());
                goodsOrderMapper.updateById(goodsOrder);
                GoodsVo goods = goodsService.getGoods(goodsOrder.getGoodsId());
                //库存增加
                goods.setStock(goods.getStock() + 1);
                goodsService.updateGoods(goods);
                //卖家金额减少
                walletService.subtractBalance(goodsOrder.getGoodsPrice(), goodsOrder.getSellerId());
                //买家余额增加
                walletService.addBalance(goodsOrder.getGoodsPrice(), goodsOrder.getBuyerId());
                //记录日志
                //删除买家日志
                walletLogService.deleteLog(goodsOrder.getSellerId(), goodsOrder.getOrderNo());
                walletLogService.deleteLog(goodsOrder.getBuyerId(), goodsOrder.getOrderNo());
                walletLogService.insertLog(goodsOrder.getGoodsPrice(), goodsOrder.getBuyerId(), goodsOrder.getOrderNo(), WalletEnums.WalletTypeEnums.REFUND, WalletEnums.WalletLogStatusEnums.RECEIVED);
            });
        } finally {
            cacheService.unLock(CacheKey.WALLET + ":" + goodsOrder.getBuyerId());
            cacheService.unLock(CacheKey.WALLET + ":" + goodsOrder.getSellerId());
        }
    }

    @Override
    public void deleteOrder(String orderNo) {
        GoodsOrder buyerOrder = getBuyerOrder(orderNo, LoginContext.getContext().getId());
        AssertUtils.isNotNull(buyerOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        if (!OrderEnums.OrderStatus.REFUND.getCode().equals(buyerOrder.getStatus())
                && !OrderEnums.OrderStatus.FINISHED.getCode().equals(buyerOrder.getStatus())
                && !OrderEnums.OrderStatus.CANCEL.getCode().equals(buyerOrder.getStatus())) {
            throw new BusinessException(CampusErrorEnums.ORDER_STATUS_ERROR);
        }
        buyerOrder.setBuyerShow(0);
        goodsOrderMapper.updateById(buyerOrder);
    }

    @Override
    public void deleteMeOrder(String orderNo) {
        GoodsOrder buyerOrder = getSellerOrder(orderNo, LoginContext.getContext().getId());
        AssertUtils.isNotNull(buyerOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        if (!OrderEnums.OrderStatus.REFUND.getCode().equals(buyerOrder.getStatus())
                && !OrderEnums.OrderStatus.FINISHED.getCode().equals(buyerOrder.getStatus())
                && !OrderEnums.OrderStatus.CANCEL.getCode().equals(buyerOrder.getStatus())) {
            throw new BusinessException(CampusErrorEnums.ORDER_STATUS_ERROR);
        }
        buyerOrder.setSellerShow(0);
        goodsOrderMapper.updateById(buyerOrder);
    }

    /**
     * 锁定买家与卖家钱包
     *
     * @param buyer  买家
     * @param seller 卖家
     */
    private void lockSellerAndBuyerWallet(Long buyer, Long seller) {
        boolean lock = cacheService.lock(CacheKey.WALLET + ":" + buyer, 1000 * 2);
        boolean lock2 = cacheService.lock(CacheKey.WALLET + ":" + seller, 1000 * 2);
        if (!lock || !lock2) {
            throw new BusinessException(CommonEnums.CONCURRENT_ERROR);
        }
    }

    /**
     * 买家获取订单
     *
     * @param orderNo 订单编号
     * @param userId  用户id
     * @return 订单
     */
    private GoodsOrder getBuyerOrder(String orderNo, Long userId) {
        GoodsOrder goodsOrder = goodsOrderMapper.queryBuyerOrder(orderNo, userId);
        AssertUtils.isNotNull(goodsOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        return goodsOrder;
    }

    /**
     * 卖家获取订单
     *
     * @param orderNo 订单编号
     * @param userId  用户id
     * @return 订单
     */
    private GoodsOrder getSellerOrder(String orderNo, Long userId) {
        GoodsOrder goodsOrder = goodsOrderMapper.querySellerOrder(orderNo, userId);
        AssertUtils.isNotNull(goodsOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        return goodsOrder;
    }

    /**
     * 创建订单
     *
     * @param req     请求
     * @param context 上下文
     * @param goods   商品
     * @param address 地址
     * @return 订单
     */
    private GoodsOrder buildOrder(GoodsOrderCreateReq req, LoginInfo context, GoodsVo goods, Address address) {
        GoodsOrder goodsOrder = new GoodsOrder();
        goodsOrder.setOrderNo(String.valueOf(SnowflakeIdWorker.getId()));
        goodsOrder.setGoodsId(goods.getId());
        goodsOrder.setGoodsName(goods.getName());
        goodsOrder.setGoodsCategory(goods.getCategoryName());
        goodsOrder.setGoodsPrice(new BigDecimal(goods.getPrice()));
        goodsOrder.setBuyerId(context.getId());
        goodsOrder.setBuyerName(context.getNickname());
        goodsOrder.setSellerId(goods.getPublisher());
        goodsOrder.setSellerName(goods.getNickname());
        goodsOrder.setAddress(JSON.toJSONString(address));
        goodsOrder.setPayStatus(OrderEnums.OrderPayStatus.UNPAID.getCode());
        goodsOrder.setStatus(OrderEnums.OrderStatus.UNFINISHED.getCode());
        goodsOrder.setRemark(req.getRemark());
        return goodsOrder;
    }
}
